import random
from typing import List

# 简单神经网络模型:
# 由一个具有两个输入和一个输出的神经元组成。
# 该神经元使用Sigmoid函数作为其激活函数。
# 该神经网络使用梯度下降来最小化预测输出和期望输出之间的均方误差。

# 这个简单的神经网络模型只有一个神经元，它具有两个输入和一个输出。这种结构对于解决异或问题（即输入为[0, 0]和[1, 1]时输出为0，输入为[0, 1]和[1, 0]时输出为1）来说过于简单。
# 异或问题是非线性可分的，这意味着无法使用单个线性分类器（如单个神经元）来解决它。要解决异或问题，需要使用更复杂的神经网络结构，例如具有至少一个隐藏层的多层感知器。
# 在您提供的代码中，由于神经网络结构过于简单，因此无法正确解决异或问题。即使我们增加训练次数并调整学习率，最终结果仍然接近0.5，而不是期望的0或1。

# 此函数以浮点数作为输入，并返回输入的Sigmoid值。
def sigmoid(x: float) -> float:
    return 1 / (1 + pow(2.71828, -x))

# 此函数以浮点数作为输入，并返回该点的Sigmoid函数的导数。
def derivative_sigmoid(x: float) -> float:
    return sigmoid(x) * (1 - sigmoid(x))

# 这个类表示神经网络中的单个神经元。它有两个属性：weights和bias。 feedforward方法接受一组输入，将每个输入乘以其对应的权重，加上偏差，并将结果通过Sigmoid函数传递以产生神经元的输出。
class Neuron:
    def __init__(self, weights: List[float], bias: float):
        self.weights = weights
        self.bias = bias

    # 对于每个神经元，输入的数量必须与权重的数量相同。这是因为每个输入都与一个权重相对应，输入与其对应的权重相乘，然后将所有乘积相加以计算神经元的输入加权和，每个神经元只有一个偏差（bias）
    def feedforward(self, inputs: List[float]) -> float:
        total = sum([weight * input for weight, input in zip(self.weights, inputs)]) + self.bias
        return sigmoid(total)

# 这个类表示神经网络。它有一个属性：neuron，它是Neuron类的一个实例。
class NeuralNetwork:
    def __init__(self):

        # weights, bias 开始为随机值
        weights = [random.uniform(-1, 1) for _ in range(2)]
        bias = random.uniform(-1, 1)
        self.neuron = Neuron(weights, bias)

    # 接受一组输入并将它们传递给神经元的feedforward方法以产生神经网络的输出。
    def feedforward(self, inputs: List[float]) -> float:
        return self.neuron.feedforward(inputs)

    # 方法接受一组输入，一组期望输出，学习率和若干个时期作为参数。它使用梯度下降来训练神经网络，以最小化预测输出和期望输出之间的均方误差。
    def train(self, inputs: List[List[float]], expected_outputs: List[float], learning_rate: float, epochs: int):
        for epoch in range(epochs):

            epoch_error = 0

            for input, expected_output in zip(inputs, expected_outputs):

                output = self.feedforward(input)

                # 计算预测输出与期望输出之间的误差。
                error = output - expected_output

                epoch_error += error ** 2

                # 计算误差关于预测输出的导数。由于我们使用均方误差作为损失函数，因此误差关于预测输出的导数为2 * error。
                d_error_d_output = 2 * error

                # 计算预测输出关于神经元输入加权和的导数。由于我们使用Sigmoid函数作为激活函数，因此预测输出关于输入加权和的导数为Sigmoid函数在输入加权和处的导数。
                d_output_d_total = derivative_sigmoid(sum([weight * i for weight, i in zip(self.neuron.weights, input)]) + self.neuron.bias)

                # 计算神经元输入加权和关于权重的导数。由于输入加权和是所有输入与其对应权重的乘积之和，因此输入加权和关于每个权重的导数等于对应的输入。
                d_total_d_weights = input

                # 使用链式法则计算误差关于每个权重的导数。误差关于每个权重的导数等于误差关于预测输出的导数乘以预测输出关于输入加权和的导数再乘以输入加权和关于该权重的导数。
                d_error_d_weights = [d_error_d_output * d_output_d_total * d_total_d_weight for d_total_d_weight in d_total_d_weights]

                # 计算神经元输入加权和关于偏差的导数。由于偏差是直接添加到输入加权和上的，因此输入加权和关于偏差的导数为1。
                d_total_d_bias = 1
                d_error_d_bias = d_error_d_output * d_output_d_total * d_total_d_bias
                for i in range(len(self.neuron.weights)):
                    self.neuron.weights[i] -= learning_rate * d_error_d_weights[i]
                self.neuron.bias -= learning_rate * d_error_d_bias

            # 在循环结束后，将epoch_error除以输入数量以获得均方误差。
            print(f'Epoch {epoch}: Mean Squared Error = {epoch_error / len(inputs)}')

network = NeuralNetwork()
inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
expected_outputs = [0, 1, 1, 0]
learning_rate = 0.001
epochs = 1000000
network.train(inputs, expected_outputs, learning_rate, epochs)
for input in inputs:
    print(network.feedforward(input))
